package org.xiaog.fastdao;

import org.xiaog.fastdao.annotation.SqlType;
import org.xiaog.fastdao.util.*;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.*;

/**
 * @author xiaog
 */
public class DaoProxy implements InvocationHandler {

    private Class<?> clazz;

    public void setClazz(Class<?> clazz) {
        this.clazz = clazz;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        SqlParser sqlParser = SqlParser.getInstance(method,clazz,args);
        String sql = sqlParser.getSql();
        if (sqlParser.getType() == SqlType.SELECT) {
            return query(method, sqlParser);
        } else {
            return SaveUtil.save(sql, sqlParser.getArgs());
        }
    }

    private Object query(Method method, SqlParser parser) {
        String sql = parser.getSql();
        Object[] args = parser.getArgs();
        Class<?> returnType = parser.getReturnType();
        boolean isCollection = Collection.class.isAssignableFrom(returnType) || Collection.class == returnType;
        boolean isArray = returnType.isArray();
        Class<?> tClass;
        if (!parser.isUserMethod()) {
            returnType = parser.getReturnType();
            tClass = clazz;
        }else {
            if (isArray || isCollection) {
                if (isArray) {
                    tClass = returnType.getComponentType();
                }else {
                    ParameterizedType pt = (ParameterizedType) method.getGenericReturnType();
                    tClass = (Class<?>) pt.getActualTypeArguments()[0];
                }
            }else {
                tClass = returnType;
            }
        }
        Object result;
        if (isCollection || isArray) {

            switch (typeJudge(tClass)) {
                case MAP:
                    result = QueryUtil.queryMapCollection(sql, args);
                    break;
                case BEAN:
                    result = QueryUtil.queryBeanCollection(sql, tClass, args);
                    break;
                case SINGLE:
                    result = QueryUtil.querySingleCollection(sql, tClass, args);
                    break;
                default:
                    result = null;
                    break;
            }
            if (isArray) {
                List list = (List) result;
                int len = list.size();
                result = Array.newInstance(tClass, len);
                for (int i = 0; i < len; i++) {
                    Array.set(result, i, list.get(i));
                }
            } else {
                if (Set.class.isAssignableFrom(returnType)) {
                    result = new HashSet<>((Collection) result);
                }
            }
        } else {
            switch (typeJudge(returnType)) {
                case MAP:
                    result = QueryUtil.queryMap(sql, false, args);
                    break;
                case BEAN:
                    result = QueryUtil.queryBean(sql, returnType, false, args);
                    break;
                case SINGLE:
                    result = QueryUtil.querySingle(sql, returnType, false, args);
                    break;
                default:
                    result = null;
                    break;
            }
        }
        return result;
    }

    private ColumnType typeJudge(Class<?> eClass) {
        if (Map.class.isAssignableFrom(eClass)) {
            return ColumnType.MAP;
        } else if (eClass.isPrimitive()
                || eClass.equals(String.class)
                || Date.class.isAssignableFrom(eClass)
                || Number.class.isAssignableFrom(eClass)) {
            return ColumnType.SINGLE;
        } else {
            return ColumnType.BEAN;
        }
    }

    enum ColumnType {
        BEAN,
        SINGLE,
        MAP
    }

}
